/*
 * DDR driver for Synopsys DWC DDR PHY.
 * Used by Jz4775, JZ4780, X1000...
 *
 * Copyright (C) 2013 Ingenic Semiconductor Co.,Ltd
 * Author: Zoro <ykli@ingenic.cn>
 *
 * SPDX-License-Identifier:	GPL-2.0+
 */

#include <common.h>
#include <asm/io.h>
#include <asm/arch/x1000.h>
#include <asm/arch/x1000_dram.h>
#include <ddr/ddr_chips.h>
#include <ddr/ddrc.h>

void ddr_controller_init(int bypass)
{
	void __iomem *ddrc_regs = (void __iomem *)DDRC_BASE;

	writel(0, ddrc_regs + DDRC_CTRL);
	/* DDRC CFG init*/
	writel(0x0a668a40, ddrc_regs + DDRC_CFG);
	/* DDRC timing init*/
	writel(0x04070301, ddrc_regs + DDRC_TIMING(1));
	writel(0x04080303, ddrc_regs + DDRC_TIMING(2));
	writel(0x2003020b, ddrc_regs + DDRC_TIMING(3));
	writel(0x07630121, ddrc_regs + DDRC_TIMING(4));
	writel(0xff070100, ddrc_regs + DDRC_TIMING(5));
	writel(0x06000505, ddrc_regs + DDRC_TIMING(6));

	/* DDRC memory map configure*/
	writel(0x000020fc, ddrc_regs + DDRC_MMAP0);
	writel(0x00002400, ddrc_regs + DDRC_MMAP1);
	writel(DDRC_CTRL_CKE | DDRC_CTRL_ALH, ddrc_regs + DDRC_CTRL);
	writel(0x002f0001, ddrc_regs + DDRC_REFCNT);
	writel(0x0000c91a, ddrc_regs + DDRC_CTRL);

	/* memory remap */
	writel(0x03020d0c, ddrc_regs + DDRC_REMAP(1));
	writel(0x07060504, ddrc_regs + DDRC_REMAP(2));
	writel(0x0b0a0908, ddrc_regs + DDRC_REMAP(3));
	writel(0x0f0e0100, ddrc_regs + DDRC_REMAP(4));
	writel(0x13121110, ddrc_regs + DDRC_REMAP(5));

	writel(readl(ddrc_regs + DDRC_STATUS) & ~DDRC_STATUS_MISS, ddrc_regs + DDRC_STATUS);

	writel(0x00000000, ddrc_regs + DDRC_AUTOSR_EN);
}

static int ddr_training_hardware(int bypass)
{
	void __iomem *ddrc_regs = (void __iomem *)DDRC_BASE;
	int result = 0;
	unsigned int wait_val = 0;
	unsigned int pir_val = DDRP_PIR_INIT | DDRP_PIR_QSTRN;

	wait_val = DDRP_PGSR_IDONE | DDRP_PGSR_DTDONE;
	if(bypass) {
		pir_val |= DDRP_PIR_DLLBYP | DDRP_PIR_LOCKBYP;
	}
	writel(pir_val, ddrc_regs + DDRP_PIR);
	while((readl(ddrc_regs + DDRP_PGSR) & wait_val) != wait_val);
	result = readl(ddrc_regs + DDRP_PGSR);
	if (!(result & (DDRP_PGSR_DTERR | DDRP_PGSR_DTIERR)))
		result = 0;

	return result;
}

static void ddr_phy_init_dram(int bypass)
{
	void __iomem *ddrc_regs = (void __iomem *)DDRC_BASE;
	unsigned int pir_val;
	unsigned int val;
	unsigned int wait_val = 0;

	pir_val = DDRP_PIR_INIT | DDRP_PIR_DLLSRST | DDRP_PIR_ITMSRST | DDRP_PIR_DRAMINT | DDRP_PIR_DLLLOCK | DDRP_PIR_ZCAL;
	wait_val = DDRP_PGSR_IDONE | DDRP_PGSR_ZCDONE | DDRP_PGSR_DIDONE | DDRP_PGSR_DLDONE;

	pir_val &= ~(DDRP_PIR_ZCAL);
	wait_val &= ~(DDRP_PGSR_ZCDONE);

	if(bypass) {
		pir_val &= ~DDRP_PIR_DLLLOCK;
		pir_val &= ~DDRP_PIR_DLLSRST;
		pir_val |= DDRP_PIR_DLLBYP | DDRP_PIR_LOCKBYP;
		// DLL Disable: only bypassmode
		writel(0x1 << 31, ddrc_regs + DDRP_ACDLLCR);
		// 200M bypass.
		writel(readl(ddrc_regs + DDRP_DLLGCR) | (1 << 23), ddrc_regs + DDRP_DLLGCR);

		/*  LPDLLPD:  only for ddr bypass mode
		 * Low Power DLL Power Down: Specifies if set that the PHY should respond to the *
		 * DFI low power opportunity request and power down the DLL of the PHY if the *
		 * wakeup time request satisfies the DLL lock time */
		writel(readl(ddrc_regs + DDRP_DSGCR) & ~(1 << 4), ddrc_regs + DDRP_DSGCR);
		wait_val &= ~DDRP_PGSR_DLDONE;
	}

	val = DDRP_PGSR_IDONE | DDRP_PGSR_DLDONE | DDRP_PGSR_ZCDONE;
	while((readl(ddrc_regs + DDRP_PGSR) & val) != val);
	writel(pir_val, ddrc_regs + DDRP_PIR);
	while((readl(ddrc_regs + DDRP_PGSR) & wait_val) != wait_val);
}

static void force_no_slefresh(void)
{
	void __iomem *ddrc_regs = (void __iomem *)DDRC_BASE;

	/* force CKE1 CS1 HIGH */
	writel(0x0a668a40 | DDRC_CFG_CS1EN |  DDRC_CFG_CS0EN, ddrc_regs + DDRC_CFG);
	writel((1 << 1), ddrc_regs + DDRC_CTRL);
}
void ddr_phy_init(int bypass)
{
	void __iomem *ddrc_regs = (void __iomem *)DDRC_BASE;

	writel(0x00150000, ddrc_regs + DDRP_DTAR);

	/* DDR training address set*/
	writel(DDRP_DCR_TYPE_MDDR | DDRP_DCR_DDR8BNK_DIS, ddrc_regs + DDRP_DCR);

	writel(0x00000033, ddrc_regs + DDRP_MR0);
	writel(0x00000098, ddrc_regs + DDRP_MR2);

	writel(0x0021000a, ddrc_regs + DDRP_PTR0);
	writel(0x06009c40, ddrc_regs + DDRP_PTR1);
	writel(0x042da072, ddrc_regs + DDRP_PTR2);

	writel(0x16483352, ddrc_regs + DDRP_DTPR0);
	writel(0x000f0090, ddrc_regs + DDRP_DTPR1);
	writel(0x10010818, ddrc_regs + DDRP_DTPR2);

	writel(0x01042e03, ddrc_regs + DDRP_PGCR);

	writel(0x00090881, ddrc_regs + DDRP_DXGCR(0));
	writel(0x00090881, ddrc_regs + DDRP_DXGCR(1));
	writel(0x00090e80, ddrc_regs + DDRP_DXGCR(2));
	writel(0x00090e80, ddrc_regs + DDRP_DXGCR(3));

	writel(0x30c00813, ddrc_regs + DDRP_ACIOCR);
	writel(0x00004912, ddrc_regs + DDRP_DXCCR);

	ddr_phy_init_dram(bypass);

	/* reset DDR ctrl */
	writel(0x2 << 21, ddrc_regs + DDRC_PHYRST_CFG);
	udelay(1000);
	writel(0, ddrc_regs + DDRC_PHYRST_CFG);
	udelay(1000);

	force_no_slefresh();
	ddr_training_hardware(bypass);
}

void sdram_init(void)
{
	void __iomem *ddrc_regs = (void __iomem *)DDRC_BASE;
	void __iomem *cpm_regs = (void __iomem *)CPM_BASE;
	unsigned int bypass = 0;
	uint32_t ddr_cdr;

	/* setup DDR clock */
	ddr_cdr = CONFIG_SYS_MPLL_FREQ / CONFIG_SYS_MEM_FREQ - 1;
	writel(ddr_cdr | CPM_DDRCDR_DCS_MPLL | CPM_DDRCDR_CE, cpm_regs + CPM_DDRCDR);
	while (readl(cpm_regs + CPM_DDRCDR) & CPM_DDRCDR_DDR_BUSY);

	if(CONFIG_SYS_MEM_FREQ < 300000000)
		bypass = 1;

	/*
	 * WARNING: 2015-01-08
	 * DDR CLK GATE(CPM_DRCG 0xB00000D0), BIT6 must set to 1 (or 0x40).
	 * If clear BIT6, chip memory will not stable, gpu hang occur.
	 */
	/* auto DDR clk gating */
	writel(0x73 | (1 << 6), cpm_regs + CPM_DRCG);
	udelay(1000);
	writel(0x71 | (1 << 6), cpm_regs + CPM_DRCG);
	udelay(1000);

	/* reset phy dll dfi and cfg */
	writel(0xd << 21 | 1, ddrc_regs + DDRC_PHYRST_CFG);
	udelay(1000);
	writel(0, ddrc_regs + DDRC_PHYRST_CFG);
	udelay(1000);

	/* DDR PHY init*/
	ddr_phy_init(bypass);
	/* DDR Controller init*/
	ddr_controller_init(bypass);

	if(bypass)
		writel(readl(cpm_regs + CPM_DDRCDR) | CPM_DDRCDR_GATE_EN, cpm_regs + CPM_DDRCDR);
}

DECLARE_GLOBAL_DATA_PTR;

int dram_init(void)
{
	/* SDRAM size was calculated when compiling. */
#ifndef EMC_LOW_SDRAM_SPACE_SIZE
#define EMC_LOW_SDRAM_SPACE_SIZE 0x10000000 /* 256M */
#endif /* EMC_LOW_SDRAM_SPACE_SIZE */

	void __iomem *ddrc_regs = (void __iomem *)DDRC_BASE;
	unsigned int ram_size;

	ddrc_cfg_t ddrc_cfg = {.d32 = readl(ddrc_regs + DDRC_CFG)};
	uint16_t col0 = ddrc_cfg.b.COL0 + 8, row0 = ddrc_cfg.b.ROW0 + 12;
	uint8_t dw = ddrc_cfg.b.DW ? 4 : 2, ba0 = ddrc_cfg.b.BA0 ? 8 : 4;
	uint32_t chip_0_szie = (uint32_t)(1 << (col0 + row0)) * dw * ba0;
	ram_size = chip_0_szie;
	if (ram_size > EMC_LOW_SDRAM_SPACE_SIZE)
		ram_size = EMC_LOW_SDRAM_SPACE_SIZE;

	gd->ram_size = ram_size;

	return 0;
}
